﻿using System;
using System.Collections.Specialized;
using System.IO.Ports;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

namespace UDS.Management.SMS
{
    public class SmsSender : ISmsSender
    {
        private SerialPort _serialPort;
        private string _smsCenter;
        private string _confirmedSmsCenter;
        private byte[] _readBuffer = new byte[1024];

        public string SmsCenter
        {
            get { return _smsCenter; }
            set { _smsCenter = value; }
        }

        /// <summary>
        /// Create the SmsSender, and initialize the SerialPort with the configured information.
        /// </summary>
        public SmsSender(NameValueCollection appSettings)
        {
            // Create an instance of SerialPort
            _serialPort = new SerialPort();
            _serialPort.PortName = appSettings["SerialPort_PortName"];
            _serialPort.BaudRate = int.Parse(appSettings["SerialPort_BaudRate"]);
            _serialPort.Parity = (Parity)Enum.Parse(typeof(Parity), appSettings["SerialPort_Parity"]);
            _serialPort.DataBits = int.Parse(appSettings["SerialPort_DataBits"]); ;
            _serialPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), appSettings["SerialPort_StopBits"]); ;
            _serialPort.Handshake = (Handshake)Enum.Parse(typeof(Handshake), appSettings["SerialPort_Handshake"]); ;
            _serialPort.ReadTimeout = 5000;
            _serialPort.WriteTimeout = 500;
        }

        public void Open()
        {
            Logger.WriteLine("Open the serial port");
            _serialPort.Open();

            // Read the SMS center number
            _confirmedSmsCenter = GetSMSCenter();
            if (string.IsNullOrEmpty(_confirmedSmsCenter))
            {
                _confirmedSmsCenter = _smsCenter;
                if (string.IsNullOrEmpty(_confirmedSmsCenter))
                {
                    throw new InvalidOperationException("SmsCenter was not configured.");
                }
            }
            Logger.WriteLine(string.Format("The SMS Center number: {0}", _confirmedSmsCenter));

            // Set PDU mode. 0: PDU mode, 1: text mode
            Logger.WriteLine("Setting PDU mode: AT+CMGF");
            _serialPort.WriteLine("AT+CMGF=0\r");
            Thread.Sleep(200);
            string response = ReadSerialPortResponse();
            Logger.WriteLine(string.Format("CMGF: {0}", response));
        }

        public void Close()
        {
            Logger.WriteLine("Close the serial port");
            _serialPort.Close();
        }

        public void Dispose()
        {
            if (_serialPort != null)
            {
                _serialPort.Dispose();
            }
        }

        private string ReadSerialPortResponse()
        {
            StringBuilder sb = new StringBuilder();
#if false
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    string line = _serialPort.ReadLine();
                    sb.Append(line);
                    sb.Append(NewLine);
                }
                catch (TimeoutException)
                { }
            }
#else
            while (true)
            {
                try
                {
                    int len = _serialPort.Read(_readBuffer, 0, _readBuffer.Length);
                    if (len > 0)
                    {
                        string line = Encoding.ASCII.GetString(_readBuffer, 0, len);
                        sb.Append(line);
                    }
                }
                catch (TimeoutException)
                {
                    break;
                }
            }
#endif

            return sb.ToString();
        }


        /// <summary>
        /// Send a SMS to the target phone.
        /// </summary>
        public bool SendSMS(string target, string msg)
        {
            if (string.IsNullOrEmpty(target))
            {
                throw new ArgumentNullException("target");
            }
            if (string.IsNullOrEmpty(msg))
            {
                throw new ArgumentNullException("msg");
            }

            const int MaxLenPerMsg = 70;
            if (msg.Length <= MaxLenPerMsg)
            {
                return SendSingleSMS(target, msg);
            }
            else
            {
                int pos = 0;
                bool succeeded = true;
                while (pos < msg.Length)
                {
                    int len = Math.Min(msg.Length - pos, MaxLenPerMsg);
                    if (!SendSingleSMS(target, msg.Substring(pos, len)))
                    {
                        succeeded = false;
                    }

                    pos += len;
                }

                return succeeded;
            }
        }


        private bool SendSingleSMS(string target, string msg)
        {
            Logger.WriteLine(string.Format("Send SMS to {0}: {1}", target, msg));
            string formatted = SmsUtility.Sms2Pdu(_confirmedSmsCenter, target, msg);

            _serialPort.Write(string.Format("AT+CMGS={0}\r", msg.Length * 2 + 15));
            Thread.Sleep(200);
            string response = ReadSerialPortResponse();
            Logger.WriteLine("AT+CMGS: " + response);

            _serialPort.WriteLine(formatted + "\x001a\r");
            Thread.Sleep(1000);
            string statusMsg = ReadSerialPortResponse();
            Logger.WriteLine("Send response: " + statusMsg);

            if (statusMsg.IndexOf("OK") >= 0)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Read the default SMS Center number from the modem using AT command.
        /// </summary>
        private string GetSMSCenter()
        {
            // Send a AT+CSCA command to get the SMS center number
            _serialPort.Write("AT+CSCA?\r");
            string response = ReadSerialPortResponse();
            if (!string.IsNullOrEmpty(response))
            {
                Regex regex = new Regex(@"\d{11,}", RegexOptions.Compiled | RegexOptions.Singleline);
                Match match = regex.Match(response);
                if (match.Success)
                {
                    return match.Value;
                }
            }

            return string.Empty;
        }
    }
}
